// Copyright 2011-2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.security.zynamics.zylib.gui.ProgressDialogs;

import com.google.common.base.Preconditions;
import com.google.security.zynamics.zylib.gui.SwingInvoker;
import com.google.security.zynamics.zylib.resources.Constants;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;

public class CProgressPanel extends JPanel {
  private static final long serialVersionUID = 8176524035621381995L;

  private final JLabel m_label = new JLabel();

  private final JProgressBar m_progressBar = new JProgressBar();

  private String m_description;

  private final ActionListener m_listener = new InternalActionListener();

  private final ActionListener m_externalCancelButtonListener;

  private final Timer m_timer = new Timer(1000, m_listener);

  private int m_seconds = 0;

  private final boolean m_showSeconds;

  public CProgressPanel(
      final String description, final boolean indeterminate, final boolean showCancelButton) {
    this(description, indeterminate, true, showCancelButton);
  }

  public CProgressPanel(
      final String description,
      final boolean indeterminate,
      final boolean showSeconds,
      final ActionListener cancelButtonListener) {
    m_externalCancelButtonListener =
        Preconditions.checkNotNull(
            cancelButtonListener, "Error: Cancel button listener can't be null.");

    m_description = description;
    m_showSeconds = showSeconds;

    createPanel(indeterminate, showSeconds, true, true);
  }

  public CProgressPanel(
      final String description,
      final boolean indeterminate,
      final boolean showSeconds,
      final boolean showCancelButton) {
    m_description = description;
    m_showSeconds = showSeconds;

    m_externalCancelButtonListener = null;

    createPanel(indeterminate, showSeconds, showCancelButton, false);
  }

  public CProgressPanel(
      final String description,
      final boolean indeterminate,
      final boolean showSeconds,
      final boolean border,
      final boolean showCancelButton) {
    m_description = description;
    m_showSeconds = showSeconds;

    m_externalCancelButtonListener = null;

    createPanel(indeterminate, showSeconds, showCancelButton, border);
  }

  private static String convertTextToHtml(final String text) {
    return "<html>" + text.replaceAll("\n", "<br>") + "</html>";
  }

  private void createPanel(
      final boolean indeterminate,
      final boolean showSeconds,
      final boolean showCancelButton,
      final boolean addBorder) {
    setLayout(new BorderLayout());

    final JPanel pPb = new JPanel(new BorderLayout());
    pPb.setBorder(new TitledBorder(""));

    if (m_description == null) {
      m_label.setVisible(false);
    } else {
      m_label.setText(convertTextToHtml(m_description));
    }

    pPb.add(m_label, BorderLayout.NORTH);

    m_progressBar.setIndeterminate(indeterminate);
    m_progressBar.setStringPainted(true);

    final JPanel borderPanel = new JPanel(new BorderLayout());

    if (addBorder) {
      borderPanel.setBorder(
          BorderFactory.createCompoundBorder(
              new LineBorder(Color.GRAY), new EmptyBorder(1, 1, 1, 1)));
    }

    if (showCancelButton) {
      final JPanel buttonPanel = new JPanel(new BorderLayout());

      final JButton cancelButton = new JButton(new CancelAction());
      cancelButton.setFocusable(false);

      final JPanel paddingPanel = new JPanel(new BorderLayout());
      paddingPanel.setBorder(new EmptyBorder(0, 1, 0, 0));
      paddingPanel.setMinimumSize(new Dimension(1, 0));

      buttonPanel.add(paddingPanel, BorderLayout.WEST);
      buttonPanel.add(cancelButton, BorderLayout.EAST);

      borderPanel.add(buttonPanel, BorderLayout.EAST);
    }

    borderPanel.add(m_progressBar, BorderLayout.CENTER);
    pPb.add(borderPanel, BorderLayout.CENTER);

    if (indeterminate && showSeconds) {
      updateSecondsText();

      m_timer.setRepeats(true);
    }

    add(pPb, BorderLayout.NORTH);
  }

  private void updateSecondsText() {
    m_progressBar.setString(String.format("%d seconds", ++m_seconds));
  }

  protected void closeRequested() {}

  public String fitTextToLabel(String text) {
    final FontMetrics metrics = m_label.getFontMetrics(m_label.getFont());
    final double labelWidth = getWidth() - 50;

    boolean fits = false;
    do {
      final double textWidth = metrics.stringWidth(text);

      if (labelWidth > textWidth) {
        fits = true;
      } else {
        if (text.length() > 4) {
          text = text.substring(0, text.length() - 4) + "...";
        } else {
          break;
        }
      }

    } while (!fits && (text.length() > 4));

    return text;
  }

  public void next() {
    m_progressBar.setValue(m_progressBar.getValue() + 1);
  }

  public void reset() {
    m_progressBar.setValue(0);
  }

  public void setMaximum(final int maximum) {
    m_progressBar.setMaximum(maximum);
  }

  public void setProgressText(final String displayString) {
    m_progressBar.setString(displayString);
  }

  public void setSubText(final String subDescription) {
    String text = fitTextToLabel(m_description) + "\n";
    text += fitTextToLabel(subDescription);

    m_label.setText(convertTextToHtml(text));

    new SwingInvoker() {
      @Override
      public void operation() {
        m_label.updateUI();
      }
    }.invokeLater();
  }

  public void setText(final String description) {
    m_description = description;

    String text = convertTextToHtml(description);
    text = fitTextToLabel(text);

    m_label.setText(text);

    new SwingInvoker() {
      @Override
      public void operation() {
        m_label.updateUI();
      }
    }.invokeLater();
  }

  public void setValue(final int value) {
    m_progressBar.setValue(value);
  }

  public void start() {
    if (m_progressBar.isIndeterminate() && m_showSeconds) {
      m_seconds = 0;
      updateSecondsText();
      m_timer.start();
    }
  }

  public void stop() {
    if (m_progressBar.isIndeterminate() && m_showSeconds) {
      m_timer.stop();
    }
  }

  private class CancelAction extends AbstractAction {
    private static final long serialVersionUID = 3809222494243730570L;

    public CancelAction() {
      super("", new ImageIcon(Constants.class.getResource("cancel.png")));
    }

    @Override
    public void actionPerformed(final ActionEvent e) {
      if (m_externalCancelButtonListener != null) {
        m_externalCancelButtonListener.actionPerformed(e);
      }

      closeRequested();
    }
  }

  private class InternalActionListener implements ActionListener {
    @Override
    public void actionPerformed(final ActionEvent event) {
      updateSecondsText();
    }
  }
}
